SurfaceView源码分析

SurfaceView介绍

SurfaceView是Andoird GUI系统中一种特殊的控件,它可以在非UI线程进行绘图。UI线程的绘制在view绘制流程一篇中介绍过了,本篇将对SurfaceView的绘制进行介绍。在此之前,我们看看SurfaceView的一般用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class SurfaceViewDemo extends SurfaceView
implements SurfaceHolder.Callback, Runnable {

private SurfaceHolder mHolder;
private Canvas mCanvas;
private boolean bDrawing;

public SurfaceViewDemo(Context context) {
super(context);
initView();
}

public SurfaceViewDemo(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}

public SurfaceViewDemo(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}

private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
new Thread(this).start();
}

@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}

@Override
public void run() {
while (bDrawing) {
draw();
}
}

private void draw() {
try {
mCanvas = mHolder.lockCanvas();
// draw something
} catch (Exception e) {
} finally {
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}

SurfaceView的生命周期回调surfaceCreated->SurfaceChanged->surfaceDestroyed。这里在surfaceCreated中进行绘制,这个绘制可以在单独的线程中进行,并不依赖于ui线程。至于为何这样,后面我们再解释。

首先看看surfaceview的源码,先看其成员都有哪些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SurfaceView extends View {
static private final String TAG = "SurfaceView";
static private final boolean DEBUG = false;

final ArrayList<SurfaceHolder.Callback> mCallbacks
= new ArrayList<SurfaceHolder.Callback>();

final int[] mLocation = new int[2];

final ReentrantLock mSurfaceLock = new ReentrantLock();
final Surface mSurface = new Surface(); // Current surface in use surfaceView使用的绘图表面
final Surface mNewSurface = new Surface(); // New surface we are switching to
boolean mDrawingStopped = true;

final WindowManager.LayoutParams mLayout
= new WindowManager.LayoutParams();
IWindowSession mSession;
MyWindow mWindow;
final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
final Rect mOverscanInsets = new Rect();
final Rect mContentInsets = new Rect();
final Configuration mConfiguration = new Configuration();

static final int KEEP_SCREEN_ON_MSG = 1;
static final int GET_NEW_SURFACE_MSG = 2;
static final int UPDATE_WINDOW_MSG = 3;

int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;

boolean mIsCreating = false;
……
}

在SurfaceView中有两个Surface绘图表面,所有绘制操作都是在这个绘图表面上进行的,这里有两个Surface是为了进行绘图时的前后台切换,这样当后台进行绘制时,前台可以显示之前绘制好的Surface表面。IWindowSession是用来和WMS进行会话的session,因为SurfaceView它本质上也是一个Window,它还有一个MyWindow成员,它类似于ViewRootImpl中的W对象,是用来和WMS进行通信交互的。因此它也是一个Binder对象,在WMS一端,它唯一标志了一个窗口对象Window。

SurfaceView的绘制流程

SurfaceView的绘制依然属于view树绘制的一部分,它依赖于宿主窗口,在View树的绘制流程中,我们看看SurfaceView是如何进行自身的绘制的,在performTraversals中绘制view树之前,会通知view树Attach到宿主窗口上,这是通过host.dispatchAttachedToWindow(attachInfo, 0)来实现的,这里的host即DecorView,它是个ViewGroup。在ViewGroup中又会调用子view的dispatchAttachedToWindow方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//ViewGroup
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

final int count = mChildrenCount;//子视图的数目
final View[] children = mChildren;//子视图数组
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
visibility | (child.mViewFlags & VISIBILITY_MASK));//通知每个子视图他们被添加到宿主窗口上去了
}
}

//view
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;//保存所附加窗口的信息
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();//onAttachedToWindow 让子类处理它被附加到宿主窗口时的事件

ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}

int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);//通知宿主窗口的可见性发生了变化
}
if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
// If nobody has evaluated the drawable state yet, then do it now.
refreshDrawableState();
}
needGlobalAttributesUpdate(false);
}

对于子View,它的dispatchAttachedToWindow会回调onAttachedToWindow通知view被添加到宿主window上,随后宿主窗口可见,还会通过onWindowVisibilityChanged通知可见性发生了变化。这里我们看看SurfaceView它是如何做处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//surfaceView被添加到宿主窗口时回调
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mParent.requestTransparentRegion(this);//请求父窗口设置一块透明区域 即 在父窗口视图上挖一个洞 mParent为其父类View
mSession = getWindowSession();//获取IWindowSession 这是一个binder负责和WMS通信 后续通过这个session来向WMS请求绘图表面
mLayout.token = getWindowToken();//这个token也是ViewRootImpl中的W
mLayout.setTitle("SurfaceView");
mViewVisibility = getVisibility() == VISIBLE;

if (!mGlobalListenersAdded) {
ViewTreeObserver observer = getViewTreeObserver();
observer.addOnScrollChangedListener(mScrollChangedListener);
observer.addOnPreDrawListener(mDrawListener);
mGlobalListenersAdded = true;
}
}

在SurfaceView的onAttachedToWindow中,SurfaceView会向父窗口请求设置一块透明区域,这个透明区域是为了使SurfaceView在宿主窗口中可见,因为SurfaceView被添加时它的Z序是小于宿主窗口的,即它是显示在宿主窗口下面的,要显示SurfaceView就需要在宿主窗口设置对应大小的透明区域。同时还会取到IWindowSession 用来和WMS进行通信。

1
2
3
4
5
6
7
8
9
 @Override
protected void onWindowVisibilityChanged(int visibility) {//宿主窗口的可见性发生了变化
super.onWindowVisibilityChanged(visibility);
//mWindowVisibility表示SurfaceView的宿主窗口的可见性,mViewVisibility表示SurfaceView自身的可见性。
//只有当mWindowVisibility和mViewVisibility的值均等于true的时候,mRequestedVisible的值才为true,表示SurfaceView是可见的
mWindowVisibility = visibility == VISIBLE;
mRequestedVisible = mWindowVisibility && mViewVisibility;
updateWindow(false, false);
}

关于可见性的变化,SurfaceView计算当前宿主窗口的可见性,并通过updateWindow来更新自身的窗口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
private void updateWindow(boolean force, boolean redrawNeeded) {
……
getLocationInWindow(mLocation);
final boolean creating = mWindow == null;
final boolean formatChanged = mFormat != mRequestedFormat;
final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
final boolean visibleChanged = mVisible != mRequestedVisible;

if (force || creating || formatChanged || sizeChanged || visibleChanged
|| mLeft != mLocation[0] || mTop != mLocation[1]
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {

try {
final boolean visible = mVisible = mRequestedVisible;
mLeft = mLocation[0];
mTop = mLocation[1];
mWidth = myWidth;
mHeight = myHeight;
mFormat = mRequestedFormat;

// Places the window relative
mLayout.x = mLeft;
mLayout.y = mTop;
mLayout.width = getWidth();
mLayout.height = getHeight();
if (mTranslator != null) {
mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout);
}

mLayout.format = mRequestedFormat;
mLayout.flags |=WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
| WindowManager.LayoutParams.FLAG_SCALED
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) {
mLayout.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
}
mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;

if (mWindow == null) {
Display display = getDisplay();
mWindow = new MyWindow(this);
mLayout.type = mWindowType;
mLayout.gravity = Gravity.START|Gravity.TOP;
mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets);
//为surfaceview添加窗口,对应于WMS一端
}

boolean realSizeChanged;
boolean reportDrawNeeded;

int relayoutResult;

mSurfaceLock.lock();
try {
mUpdateWindowNeeded = false;
reportDrawNeeded = mReportDrawNeeded;
mReportDrawNeeded = false;
mDrawingStopped = !visible;

relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
visible ? VISIBLE : GONE,
WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mConfiguration, mNewSurface);//计算窗口大小,分配绘图表面
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportDrawNeeded = true;
}

mSurfaceFrame.left = 0;
mSurfaceFrame.top = 0;
if (mTranslator == null) {
mSurfaceFrame.right = mWinFrame.width();
mSurfaceFrame.bottom = mWinFrame.height();
} else {
float appInvertedScale = mTranslator.applicationInvertedScale;
mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f);
mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f);
}

final int surfaceWidth = mSurfaceFrame.right;
final int surfaceHeight = mSurfaceFrame.bottom;
realSizeChanged = mLastSurfaceWidth != surfaceWidth
|| mLastSurfaceHeight != surfaceHeight;
mLastSurfaceWidth = surfaceWidth;
mLastSurfaceHeight = surfaceHeight;
} finally {
mSurfaceLock.unlock();
}

……
if (visible && mSurface.isValid()) {
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
mSurfaceCreated = true;
mIsCreating = true;
if (DEBUG) Log.i(TAG, "visibleChanged -- surfaceCreated");
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);
}
}
if (creating || formatChanged || sizeChanged
|| visibleChanged || realSizeChanged) {
if (DEBUG) Log.i(TAG, "surfaceChanged -- format=" + mFormat
+ " w=" + myWidth + " h=" + myHeight);
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
}
}
if (redrawNeeded) {
if (DEBUG) Log.i(TAG, "surfaceRedrawNeeded");
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
for (SurfaceHolder.Callback c : callbacks) {
if (c instanceof SurfaceHolder.Callback2) {
((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
mSurfaceHolder);
}
}
}
}
……
} catch (RemoteException ex) {}
}
}

对于SurfaceView来说,它拥有独立的Window,这个Window同样受WMS的管理,因此在绘制时需要将该Window添加到WMS中去,同时,它也有它自己的绘图表面,因此在绘制之前需要计算它自身的窗口大小并向WMS请求绘图表面。关于分配绘图缓冲在其它章节已做了描述,本篇不再多做介绍。

SurfaceView的挖洞过程

SurfaceView的窗口类型一般都是TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,它在添加到宿主窗口上时,会在父窗口的表面设置一个透明区域以显示SurfaceView自身的内容,这是通过requestTransparentRegion来进行的,下面我们就看看这个过程是如何完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//ViewGroup
public void requestTransparentRegion(View child) {
if (child != null) {
child.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
if (mParent != null) {
mParent.requestTransparentRegion(this);
}
}
}

//ViewRootImpl
@Override
public void requestTransparentRegion(View child) {
// the test below should not fail unless someone is messing with us
checkThread();
if (mView == child) {
mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
// Need to make sure we re-evaluate the window attributes next
// time around, to ensure the window has the correct format.
mWindowAttributesChanged = true;
mWindowAttributesChangesFlag = 0;
requestLayout();
}
}

SurfaceView请求透明区域的过程实际上为父View打上PFLAG_REQUEST_TRANSPARENT_REGIONS标记,这表示它要在宿主窗口上设置透明区域,这个过程直到ViewRootImpl,ViewRootImpl它也是ViewParent,它将调用requestLayout触发performTraversals来请求窗口重新布局和绘制,从而收集透明区域,最后通过WMS为该宿主窗口设置一个总的透明区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void performTraversals() {
……
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);

host.gatherTransparentRegion(mTransparentRegion);
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}

if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
……
}

收集透明区域也是从DecorView开始的,它调用gatherTransparentRegion来完成,这个区域是由mTransparentRegion来存储维护的。如果透明区域发生了变化这时候就需要重新设置该透明区域到WMS中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//ViewGroup.java
@Override
public boolean gatherTransparentRegion(Region region) {
// If no transparent regions requested, we are always opaque.
final boolean meOpaque = (mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0;
if (meOpaque && region == null) {
// The caller doesn't care about the region, so stop now.
return true;
}
super.gatherTransparentRegion(region);
final View[] children = mChildren;
final int count = mChildrenCount;
boolean noneOfTheChildrenAreTransparent = true;
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
if (!child.gatherTransparentRegion(region)) {
noneOfTheChildrenAreTransparent = false;
}
}
}
return meOpaque || noneOfTheChildrenAreTransparent;
}

收集透明区域会遍历父view下的所有子view的透明区域。在开始收集之前,首先将透明区域设置为DecorView视图的大小,然后遍历子view,如果view是不透明的区域则将其从初始的透明区域中移除,这样最后保留下来的就是需要设置的透明区域。

下面我们看看SurfaceView它是如何进行计算它的透明区域的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public boolean gatherTransparentRegion(Region region) {
if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
return super.gatherTransparentRegion(region);
}

boolean opaque = true;
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
// this view draws, remove it from the transparent region
opaque = super.gatherTransparentRegion(region);
} else if (region != null) {
int w = getWidth();
int h = getHeight();
if (w>0 && h>0) {
getLocationInWindow(mLocation);
// otherwise, punch a hole in the whole hierarchy
int l = mLocation[0];
int t = mLocation[1];
region.op(l, t, l+w, t+h, Region.Op.UNION);
}
}
if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
opaque = false;
}
return opaque;
}

SurfaceView首先判断窗口类型是否作为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,如果是则表示SurfaceView是用来作为应用面板的,这时候调用父类view的gatherTransparentRegion来处理,如果需要绘制,则将view的绘制区域从从参数region所描述的透明区域中移除。

SurfaceView的绘制

SurfaceView具有独立的绘图Surface,但它仍然属于View树中的子节点,它依附在宿主窗口上,所以它自身的view也是需要绘制到宿主窗口的Surface上的。在介绍view绘制流程一节中我们知道了view的绘制会触发draw和dispatchDraw方法,前者绘制它自身,后者负责绘制子view。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//SurfaceView.java
@Override
public void draw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// draw() is not called when SKIP_DRAW is set
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
}
super.draw(canvas);
}

@Override
protected void dispatchDraw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// if SKIP_DRAW is cleared, draw() has already punched a hole
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
}
super.dispatchDraw(canvas);
}

draw和dispatchDraw方法的参数canvas代表的是宿主窗口的绘图表面的画布,这表示surfaceview自身的ui是绘制在
宿主绘图表面上的,如果mWindowType不为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL表示SurfaceView不作为应用面板,那么将其view所占区域设置为黑色。

除了在宿主窗口上绘制ui,SurfaceView可以在自身的绘图表面上绘制内容,一般的绘制流程如下:

1
2
3
4
5
6
7
8
SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view);    
SurfaceHolder sh = sv.getHolder();
Cavas canvas = sh.lockCanvas()

//Draw something on canvas
......

sh.unlockCanvasAndPost(canvas);

需要注意的是,当绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS时,表示绘图表面的缓冲区不受我们控制,它是由摄像头或者视频播放服务来提供的。因此不能在随意在上面进行内容绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 @Override
public Canvas lockCanvas() {
return internalLockCanvas(null);
}

private final Canvas internalLockCanvas(Rect dirty) {
mSurfaceLock.lock();
Canvas c = null;
if (!mDrawingStopped && mWindow != null) {
try {
c = mSurface.lockCanvas(dirty);
} catch (Exception e) {
Log.e(LOG_TAG, "Exception locking surface", e);
}
}

if (c != null) {
mLastLockTime = SystemClock.uptimeMillis();
return c;
}

// If the Surface is not ready to be drawn, then return null,
// but throttle calls to this function so it isn't called more
// than every 100ms.
long now = SystemClock.uptimeMillis();
long nextTime = mLastLockTime + 100;
if (nextTime > now) {
try {
Thread.sleep(nextTime-now);
} catch (InterruptedException e) {
}
now = SystemClock.uptimeMillis();
}
mLastLockTime = now;
mSurfaceLock.unlock();

return null;
}

SurfaceView的lockCanvas是从其绘图表面申请缓冲区,也就是Canvas,应用上层可以使用这个Canvas来进行内容的绘制,需要注意的是这个画布并不是线程安全的,需要通过mSurfaceLock的锁保护。

坚持原创技术分享,您的支持将鼓励我继续创作!